package liuyang.bigeventserver.modules.security.filter;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.InputStream;

/**
 * 本组件处理post形式的login请求
 * {
 *     "username": "owen",
 *     "password": "123"
 * }
 *
 * 只是提供了一个过滤规则，配合SecurityConfig中的配置，完成对某一个REST式请求的抽取信息过程。
 *
 * 注： 对比三更草堂的方案，三更草堂把Login和Logout放在了一个独立的Controller中。独立到Controller中的方案扩展起来会更容易理解，比如增加图形验证码功能。
 *      他在39.其他认证方案畅享一节中提到了本类使用的方案，即集成UsernamePasswordAuthenticationFilter，可以听下思路。
 *      如果再这里添加验证码，则需要在本过滤器之前再添加一个过滤器，来校验验证码。
 *
 * 2024/2/28
 * 对UsernamePasswordAuthenticationFilter的初步说明（尚硅谷 环环）
 * https://www.bilibili.com/video/BV14b4y1A7Wz/?p=23&spm_id_from=pageDriver&vd_source=8bd7b24b38e3e12c558d839b352b32f4
 *
 * 2024/2/10
 * Thor的方案是把登录功能放在了这里。这样整个REST形式的登录、注销的就都不需要写Spring MVC的Controller了！业务与安全代码彻底分离！ 干脆利索
 * 编写
 * https://www.bilibili.com/video/BV1Fd4y1k7rq?p=38&vd_source=8bd7b24b38e3e12c558d839b352b32f4
 * 修正
 * https://www.bilibili.com/video/BV1Fd4y1k7rq/?p=45&spm_id_from=pageDriver&vd_source=8bd7b24b38e3e12c558d839b352b32f4
 *
 * @author liuyang(wx)
 * @since 2022/1/26
 *        2024/2/10 Thor的方案好！
 *        2024/2/27 更换为jakarta包
 *        2024/2/28 第一次联测成功
 *        2024/3/8  login时可能传递的是json，也有可能是x-www-form-urlencoded, 分开处理。
 */
//@Component // 在SecurityConfig里面进行组装， 各种行为和属性在外面指定。
@Slf4j
public class JsonLoginAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    @Autowired
    private ObjectMapper om;// final类型的初始化只能在构造函数中进行。

    // 说明：对本组件的组装、行为配置（认证成功、失败）和注册放在SecurityConfig

    // 既然是重写，就按照UsernamePasswordAuthenticationFilter.attemptAuthentication中的方法仿写即可。
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        //return json(request, response);
        return xWwwFormUrlencoded(request, response);
    }

    // json
    private Authentication json(HttpServletRequest request, HttpServletResponse response)  {
        UsernamePasswordAuthenticationToken authRequest;

        try (InputStream is = request.getInputStream()) {
            // 从reqeust传来的JSON中抽取相关字段。（读写方法可以商榷）
            JsonNode jsonNode = om.readTree(is);
            String username = jsonNode.get("username").textValue();
            String password = jsonNode.get("password").textValue();
            log.debug("username, password (json) = {}, {}", username, password);

            authRequest = new UsernamePasswordAuthenticationToken(username, password);
        } catch (IOException e) {
            throw new BadCredentialsException("没有找到用户名或密码参数。");
        }
        setDetails(request, authRequest);
        //return super.attemptAuthentication(request, response);// 错误!
        return this.getAuthenticationManager().authenticate(authRequest);
    }

    // 202403081245 add
    // 前端使用这种方式的场景：https://www.bilibili.com/video/BV14z4y1N7pg/?p=74&spm_id_from=pageDriver&vd_source=8bd7b24b38e3e12c558d839b352b32f4
    private Authentication xWwwFormUrlencoded(HttpServletRequest request, HttpServletResponse response)  {
        UsernamePasswordAuthenticationToken authRequest;

        String username = request.getParameter("username");
        String password = request.getParameter("password");
        log.debug("username, password (xWwwFormUrlencoded) = {}, {}", username, password);
        authRequest = new UsernamePasswordAuthenticationToken(username, password);

        // TODO 异常细节后面再处理。

        setDetails(request, authRequest);
        //return super.attemptAuthentication(request, response);// 错误!
        return this.getAuthenticationManager().authenticate(authRequest);
    }
}
